#include "OpenglAppFramework/inc/OpenglEnv.h"
#include "OpenglAppFramework/inc/Shader.h"
#include "OpenglAppFramework/inc/Utils.h"
#include <fstream>
#include <iostream>

static void readFile(const char* file_name, std::string& content) {
    std::ifstream ifs(file_name);
    if(ifs.good() == false) {
        std::cerr << "Open file failed: " << file_name << std::endl;
    }
    std::istreambuf_iterator<char> begin(ifs);
    std::istreambuf_iterator<char> end;
    content.assign(begin, end);
}

#define MSG_BUF_SIZE    (1024)
static int checkShaderCompile(const std::string& type, unsigned int shader_id) {
    int is_success;
    char msg_buf[MSG_BUF_SIZE];
    glGetShaderiv(shader_id, GL_COMPILE_STATUS, &is_success);
    if(!is_success) {
        glGetShaderInfoLog(shader_id, MSG_BUF_SIZE, NULL, msg_buf);
        std::cerr << "Shader compile error! type:" << type 
            << " message:" << msg_buf << std::endl;
        return -1;
    }
    return 0;
}

static int checkShaderLink(unsigned int program_id) {
    int is_success;
    char msg_buf[MSG_BUF_SIZE];
    glGetProgramiv(program_id, GL_LINK_STATUS, &is_success);
    if(!is_success) {
        glGetProgramInfoLog(program_id, MSG_BUF_SIZE, NULL, msg_buf);
        std::cerr << "Shader link error! message:" << msg_buf << std::endl;
        return -1;
    }
    return 0;
}

Shader::Shader() {
    m_vtx_shader_id = 0;
    m_frag_shader_id = 0;
    m_program_id = 0;
}

Shader::~Shader() {}

int Shader::createAndBuild(const std::string& vtx, const std::string& frag) {
    std::string content;
    const char* ccontent;

    m_program_id = glCreateProgram();CHECK_OPENGL(__FILE__,__LINE__);
    if(vtx.empty() == false) {
        readFile(vtx.c_str(), content);
        ccontent = content.c_str();
        m_vtx_shader_id = glCreateShader(GL_VERTEX_SHADER);
        glShaderSource(m_vtx_shader_id, 1, &ccontent, NULL);
        glCompileShader(m_vtx_shader_id);
        if(checkShaderCompile("VERTEX", m_vtx_shader_id) != 0) {
            return -1;
        }
        glAttachShader(m_program_id, m_vtx_shader_id);
    }
    if(frag.empty() == false) {
        readFile(frag.c_str(), content);
        ccontent = content.c_str();
        m_frag_shader_id = glCreateShader(GL_FRAGMENT_SHADER);
        glShaderSource(m_frag_shader_id, 1, &ccontent, NULL);
        glCompileShader(m_frag_shader_id);
        if(checkShaderCompile("FRAGMENT", m_frag_shader_id) != 0) {
            return -1;
        }
        glAttachShader(m_program_id, m_frag_shader_id);
    }
    glLinkProgram(m_program_id);CHECK_OPENGL(__FILE__,__LINE__);
    if(checkShaderLink(m_program_id) != 0) {
        return -1;
    }
    return 0;
}

void Shader::use() {
    glUseProgram(m_program_id); 
}

void Shader::destroy() {
    glDeleteProgram(m_program_id);
}

uint32_t Shader::getPorgramId() const {
    return m_program_id;
}

void Shader::setBool(const std::string &name, bool value) const {         
    glUniform1i(glGetUniformLocation(m_program_id, name.c_str()), (int)value); 
}

void Shader::setInt(const std::string &name, int value) const { 
    glUniform1i(glGetUniformLocation(m_program_id, name.c_str()), value); 
}

void Shader::setUint(const std::string &name, unsigned int value) const { 
    glUniform1ui(glGetUniformLocation(m_program_id, name.c_str()), value); 
}

void Shader::setFloat(const std::string &name, float value) const { 
    glUniform1f(glGetUniformLocation(m_program_id, name.c_str()), value); 
}

void Shader::setVec2(const std::string &name, const glm::vec2 &value) const { 
    glUniform2fv(glGetUniformLocation(m_program_id, name.c_str()), 1, &value[0]); 
}
void Shader::setVec2(const std::string &name, float x, float y) const { 
    glUniform2f(glGetUniformLocation(m_program_id, name.c_str()), x, y); 
}

void Shader::setVec3(const std::string &name, const glm::vec3 &value) const { 
    glUniform3fv(glGetUniformLocation(m_program_id, name.c_str()), 1, &value[0]); 
}
void Shader::setVec3(const std::string &name, float x, float y, float z) const { 
    glUniform3f(glGetUniformLocation(m_program_id, name.c_str()), x, y, z); 
}

void Shader::setVec4(const std::string &name, const glm::vec4 &value) const { 
    glUniform4fv(glGetUniformLocation(m_program_id, name.c_str()), 1, &value[0]); 
}
void Shader::setVec4(const std::string &name, float x, float y, float z, float w) const { 
    glUniform4f(glGetUniformLocation(m_program_id, name.c_str()), x, y, z, w); 
}

void Shader::setMat2(const std::string &name, const glm::mat2 &mat) const {
    glUniformMatrix2fv(glGetUniformLocation(m_program_id, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}

void Shader::setMat3(const std::string &name, const glm::mat3 &mat) const {
    glUniformMatrix3fv(glGetUniformLocation(m_program_id, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}

void Shader::setMat4(const std::string &name, const glm::mat4 &mat) const {
    glUniformMatrix4fv(glGetUniformLocation(m_program_id, name.c_str()), 1, GL_FALSE, &mat[0][0]);
}
